iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
Vue.js

Vue & GraphQL 探險之旅:30天,從新手村到魔王之巔系列 第 12

[Day12] 實戰演練:從 Vue 開始的冒險,讓 GraphQL 查詢照亮部落格開發的征途

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20231014/20141111Ac6h3dKFpe.png

歡迎來到本系列文章的第二階段:動手做、做中學

只有透過實際操作,我們才能真正掌握知識並內化為自己的硬實力。

在這一階段,我們將一同運用 Vue 3 的 Composition APIVue Apollo,從零開始打造一個部落格 SPA 應用。在此過程中,我們不僅可以學習到在 Vue 框架中開發 GraphQL 的完整 CRUD 流程,還會接觸到從基礎到進階的 GraphQL Operation 技巧。

https://ithelp.ithome.com.tw/upload/images/20231014/2014111154RHHd2dZZ.png
部落格應用預覽

https://ithelp.ithome.com.tw/upload/images/20231014/20141111oCcEBkv7mK.png
部落格應用預覽 (RWD)

讓我們一起將學到的理論化為實踐,親眼見證這些魔法在真實世界中的運作!

文章範例程式碼 GitHub

範例程式碼 GitHub 連結

Day12 開始前分支:feat/day_12/01_blog-layout-design
Day12 進度完成分支:feat/day_12/02_implement_blog_list_view

GraphQL API Server

請參考 [Day09] 召喚伺服器:Mock GraphQL Server 與快速測試 GraphQL 技巧

我們將使用該篇文章內介紹的 GraphQLZero 作為 Fake GraphQL API

實作文章列表功能

從文章列表開始
當使用者瀏覽部落格時,第一個畫面往往決定了他們是否會繼續停留。
部落格文章列表不僅僅是一系列的文章標題,更決定了知識結構內容的組成,是吸引流量的第一道門檻。

剖析文章列表功能

部落格文章列表功能主要包含以下元素:

  1. 文章資料集:一個包含多個文章物件的陣列。我們將從 GraphQL API 獲取這個資料。
  2. 文章列表容器:展示所有文章項目。它確保所有的文章項目整齊、統一地排列,並提供一個舒適的閱讀體驗。
  3. 文章項目:展示單一文章的內容,包括但不限於標題、摘要、作者等。它的目的是讓讀者能夠一眼瞥見文章的主要內容,並決定是否點擊閱讀全文。

實作步驟

完整的範例程式碼請參考:feat/day_12/02_implement_blog_list_view

文章資料集

src/views/BlogListView.vue 中:

  1. 撰寫 GraphQL 查詢 getAllPost
  2. 使用 graphql-tag 將上述語法嵌入 TypeScript中
    • graphql-tag 是一個用於解析 GraphQL 查詢字串的 JavaScript 模板文字標籤函數,使用此套件可以確保查詢語法的正確性,並將查詢轉換為用於客戶端或伺服器操作的標準化格式。
  3. 使用 Vue Apollo 的 useQuery 來取得資料
<script setup lang="ts">
import { computed } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import gql from 'graphql-tag'
import ArticleList from '@/components/articles/ArticleList.vue'

const gqlGetAllPosts = gql`
query getAllPost {
  posts {
    data {
      id
      title
      body
      user {
        id
        name
        email
      }
      comments {
        data {
          id
          name
          email
          body
        }
      }
    }
  }
}
`

const { result, loading, error } = useQuery(gqlGetAllPosts)

const posts = computed(() => (result.value?.posts?.data ?? []))
</script>

<template>
...
</template>

我們可以使用 Apollo Client DevTool 來驗證串接是否成功:
https://ithelp.ithome.com.tw/upload/images/20231015/20141111fghD0jyic0.png

文章列表容器

在前述的文章資料集中,可以看到 useQuery 可以解構出 result, loading, error

const { result, loading, error } = useQuery(gqlGetAllPosts)
  • result: 是一個 Ref,保存由 Apollo 返回的結果中的資料。當查詢尚未成功完成時,「result」可能最初為 undefined,所以我們使用 Vue 的 computed 條件式的產生資料集 posts
  • loading: 是一個 boolean 型態的 Ref,用於追踪查詢的載入狀態。
  • error: 是一個 Ref,保存請求過程中可能發生的任何錯誤。

處理不同狀態的頁面呈現
src/views/BlogListView.vue 中,我們使用 loading, error 來呈現取得資料集的目前狀態,只有當查詢載入完畢且沒有錯誤,才會使用 ArticleList 顯示文章列表

<template>
  <div v-if="loading">
    <div role="status" class="max-w-sm animate-pulse">
      <div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4" />
      <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5" />
      <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5" />
      <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[330px] mb-2.5" />
      <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[300px] mb-2.5" />
      <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]" />
      <span class="sr-only">Loading...</span>
    </div>
  </div>
  <div v-else-if="error">
    <p>Error: {{ error.message }}</p>
  </div>
  <div v-else>
    <ArticleList :posts="posts" />
  </div>
</template>

ArticleList
使用 props 傳入的 posts,並使用 v-forArticleCard 渲染畫面

<script setup lang="ts">
import ArticleCard from '@/components/articles/ArticleCard.vue'
import type { Post } from '@/types/blogTypes'

const props = defineProps({
  posts: {
    type: Array as () => Post[],
    default: () => [],
  },

})
</script>

<template>
  <section class="bg-white dark:bg-gray-900">
    <div class="grid gap-8 lg:grid-cols-2">
      <ArticleCard v-for="post in posts" :key="post.id" :post="post" />
    </div>
  </section>
</template>

文章項目

為了模組化與重用,以及程式碼的可讀性,我們使用單獨的元件 ArticleCard 來渲染單個文章項目卡片

https://ithelp.ithome.com.tw/upload/images/20231015/20141111fNIooCdOK4.png

ArticleCard
使用 props 傳入的 post 渲染畫面

<script setup lang="ts">
import { toRefs } from 'vue'
import type { Post } from '@/types/blogTypes'

const props = defineProps<{
  post: Post
}>()

const { post } = toRefs(props)
</script>

<template>
  <article
    class="p-6 bg-white rounded-lg border border-gray-200 shadow-md dark:bg-gray-800 dark:border-gray-700"
  >
    ...
  </article>
</template>

成果

https://ithelp.ithome.com.tw/upload/images/20231015/20141111esFVGDL1qT.png


Recap

今天我們迅速完成了部落格專案的首個 GraphQL 查詢,並探討了 Vue Apollo 的 useQuery 與 Vue 框架的整合基礎。細心的讀者可能已經發現,有許多地方還有優化的空間,例如,GraphQL Schema 的 TypeScript 定義是否真的只能手動編寫?

在明天的文章中,我們將介紹如何利用 graphql-code-generator 自動生成 GraphQL Schema 的 TypeScript 定義,進一步提升開發效率!


上一篇
[Day11] 資料召喚術:深入淺出 GraphQL Operation 核心概念
下一篇
[Day13] 魔法重構:在 Vue 專案中自動生成 GraphQL 的 TypeScript 定義
系列文
Vue & GraphQL 探險之旅:30天,從新手村到魔王之巔31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言